 /*******************************************************************************
  * Copyright (c) 2005, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM - Initial API and implementation
  *******************************************************************************/
 package org.eclipse.core.internal.runtime;

 import java.io.PrintWriter ;
 import java.util.ArrayList ;
 import java.util.HashMap ;
 import org.eclipse.core.runtime.*;
 import org.eclipse.core.runtime.PerformanceStats.PerformanceListener;
 import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.osgi.framework.log.FrameworkLog;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceReference;

 /**
  * Processes, records, and performs notification of performance events
  * that occur in the system.
  */
 public class PerformanceStatsProcessor extends Job {
     private static final PerformanceStatsProcessor instance = new PerformanceStatsProcessor();

     private static final long SCHEDULE_DELAY = 2000;

     /**
      * Events that have occurred but have not yet been broadcast.
      */
     private final ArrayList changes = new ArrayList ();

     /**
      * Event failures that have occurred but have not yet been broadcast.
      * Maps (PerformanceStats -> Long).
      */
     private final HashMap failures = new HashMap ();

     /**
      * Event listeners.
      */
     private final org.eclipse.core.runtime.ListenerList listeners = new org.eclipse.core.runtime.ListenerList();

     private PlatformLogWriter log;

     /*
      * @see PerformanceStats#addListener
      */
     public static void addListener(PerformanceListener listener) {
         instance.listeners.add(listener);
     }

     /**
      * Records the fact that an event occurred.
      *
      * @param stats The event that occurred
      */
     public static void changed(PerformanceStats stats) {
         synchronized (instance) {
             instance.changes.add(stats);
         }
         instance.schedule(SCHEDULE_DELAY);
     }

     /**
      * Records the fact that an event failed.
      *
      * @param stats The event that occurred
      * @param pluginId The id of the plugin that declared the blame object, or
      * <code>null</code>
      * @param elapsed The elapsed time for this failure
      */
     public static void failed(PerformanceStats stats, String pluginId, long elapsed) {
         synchronized (instance) {
             instance.failures.put(stats, new Long (elapsed));
         }
         instance.schedule(SCHEDULE_DELAY);
         instance.logFailure(stats, pluginId, elapsed);
     }

     /*
      * @see PerformanceStats#printStats(PrintWriter)
      */
     public static void printStats(PrintWriter out) {
         /* gather totals */
         long totalTime = 0;
         int totalCount = 0;
         PerformanceStats[] allStats = PerformanceStats.getAllStats();
         for (int i = 0; i < allStats.length; i++) {
             PerformanceStats stats = allStats[i];
             totalTime += stats.getRunningTime();
             totalCount += stats.getRunCount();
         }
         //dump stats
 out.println("---------------------------------------------------------------"); //$NON-NLS-1$
 for (int i = 0; i < allStats.length; i++) {
             PerformanceStats stats = allStats[i];
             out.print("Event: "); //$NON-NLS-1$
 out.print(stats.getEvent());
             out.print(" Blame: "); //$NON-NLS-1$
 out.print(stats.getBlameString());
             if (stats.getContext() != null) {
                 out.print(" Context: "); //$NON-NLS-1$
 out.print(stats.getContext());
             }
             out.println();

             int runCount = stats.getRunCount();
             if (runCount > 0) {
                 out.print("Run count: "); //$NON-NLS-1$
 out.print(Integer.toString(runCount));
                 out.print(" ("); //$NON-NLS-1$
 out.print(Integer.toString((int) (runCount * 100.0 / totalCount)));
                 out.println(" % of total)"); //$NON-NLS-1$
 }

             long runTime = stats.getRunningTime();
             if (runTime > 0) {
                 out.print("Duration (ms): "); //$NON-NLS-1$
 out.print(Long.toString(runTime));
                 out.print(" ("); //$NON-NLS-1$
 out.print(Integer.toString((int) (runTime * 100.0 / totalTime)));
                 out.println(" % of total)"); //$NON-NLS-1$
 }
             out.println(""); //$NON-NLS-1$
 }
     }

     /*
      * @see PerformanceStats#removeListener
      */
     public static void removeListener(PerformanceListener listener) {
         instance.listeners.remove(listener);
     }

     /**
      * Private constructor to enforce singleton usage.
      */
     private PerformanceStatsProcessor() {
         super("Performance Stats"); //$NON-NLS-1$
 setSystem(true);
         setPriority(DECORATE);
         BundleContext context = PlatformActivator.getContext();
         String filter = '(' + FrameworkLog.SERVICE_PERFORMANCE + '=' + Boolean.TRUE.toString() + ')';
         ServiceReference[] references;
         FrameworkLog perfLog = null;
         try {
             references = context.getServiceReferences(FrameworkLog.class.getName(), filter);
             if (references != null && references.length > 0) {
                 //just take the first matching service
 perfLog = (FrameworkLog) context.getService(references[0]);
                 //make sure correct location is set
 IPath logLocation = Platform.getLogFileLocation();
                 logLocation = logLocation.removeLastSegments(1).append("performance.log"); //$NON-NLS-1$
 perfLog.setFile(logLocation.toFile(), false);
             }
         } catch (Exception e) {
             IStatus error = new Status(IStatus.ERROR, Platform.PI_RUNTIME, 1, "Error loading performance log", e); //$NON-NLS-1$
 InternalPlatform.getDefault().log(error);
         }
         //use the platform log if we couldn't create the performance log
 if (perfLog == null)
             perfLog = InternalPlatform.getDefault().getFrameworkLog();
         log = new PlatformLogWriter(perfLog);
     }

     /**
      * Logs performance event failures to the platform's performance log
      */
     private void logFailure(PerformanceStats stats, String pluginId, long elapsed) {
         //may have failed to get the performance log service
 if (log == null)
             return;
         if (pluginId == null)
             pluginId = Platform.PI_RUNTIME;
         String msg = "Performance failure: " + stats.getEvent() + " blame: " + stats.getBlameString() + " context: " + stats.getContext() + " duration: " + elapsed; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
 log.logging(new Status(IStatus.WARNING, pluginId, 1, msg, new RuntimeException ()), pluginId);
     }

     /*
      * @see Job#run(IProgressMonitor)
      */
     protected IStatus run(IProgressMonitor monitor) {
         PerformanceStats[] events;
         PerformanceStats[] failedEvents;
         Long [] failedTimes;
         synchronized (this) {
             events = (PerformanceStats[]) changes.toArray(new PerformanceStats[changes.size()]);
             changes.clear();
             failedEvents = (PerformanceStats[]) failures.keySet().toArray(new PerformanceStats[failures.size()]);
             failedTimes = (Long []) failures.values().toArray(new Long [failures.size()]);
             failures.clear();
         }

         //notify performance listeners
 Object [] toNotify = listeners.getListeners();
         for (int i = 0; i < toNotify.length; i++) {
             final PerformanceStats.PerformanceListener listener = ((PerformanceStats.PerformanceListener) toNotify[i]);
             if (events.length > 0)
                 listener.eventsOccurred(events);
             for (int j = 0; j < failedEvents.length; j++)
                 listener.eventFailed(failedEvents[j], failedTimes[j].longValue());
         }
         schedule(SCHEDULE_DELAY);
         return Status.OK_STATUS;
     }

     /*
      * @see Job#shouldRun()
      */
     public boolean shouldRun() {
         return !changes.isEmpty() || !failures.isEmpty();
     }
 }
